package mux

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"io"
	olog "log"
	"os"
	"strconv"
	"sync"
	"unsafe"

	"context"

	http "github.com/valyala/fasthttp"

	"xp/lib/errors"
	"xp/lib/etc"
	"xp/lib/log"
	mux "xp/lib/mux/router"
	"xp/lib/trace"
	"xp/lib/utils"
)

var (
	ctxPool = &sync.Pool{
		New: func() interface{} {
			return new(Ctx)
		},
	}
)

type Ctx struct {
	Req
	Resp
	Proto
	*http.RequestCtx
	context.Context

	name       string
	params     mux.Params
	reqID      string
	tr         *trace.Trace
	stacktrace []byte
	reason     []byte
	extended   map[string]interface{}
}

func (ctx *Ctx) Extend(key string, val interface{}) {
	if ctx.extended == nil {
		ctx.extended = make(map[string]interface{})
	}
	ctx.extended[key] = val
}

func (ctx *Ctx) GetExtended(key string) interface{} {
	if v, ok := ctx.extended[key]; ok {
		return v
	}
	return nil
}

func (ctx *Ctx) SetContext(c context.Context) {
	if ctx.tr != nil {
		ctx.Context = ctx.tr.NewContext(c)
	} else {
		ctx.Context = c
	}
}

func newCtx(ctx *http.RequestCtx, ps mux.Params, proto Proto, name string, tr bool) (*Ctx, error) {
	c := ctxPool.Get().(*Ctx)
	c.name = name
	c.params = ps
	c.Proto = proto
	c.RequestCtx = ctx
	if reqID := ctx.Request.Header.Peek(trace.XReqID); len(reqID) != 0 {
		c.reqID = utils.Bytes2Str(reqID)
	} else {
		c.reqID = utils.NewRandom()
	}
	if tr {
		var parent int64
		if traceID := ctx.Request.Header.Peek(trace.XTrace); len(traceID) != 0 {
			parent, _ = strconv.ParseInt(utils.Bytes2Str(traceID), 10, 64)
		}
		c.tr = trace.New(parent, c.reqID, name)
	}
	if proto != nil {
		req, err := proto.ReadRequest(c)
		if err != nil {
			if etc.ModeDev() {
				c.Request.WriteTo(os.Stderr)
			}
			c.Error(err)
			return c, err
		}
		c.Req = req
	}
	return c, nil
}

func (ctx *Ctx) Name() string {
	return ctx.name
}

func (ctx *Ctx) ReqID() string {
	return ctx.reqID
}

func (ctx *Ctx) release() {
	ctx.params = nil
	ctx.extended = nil
	ctx.Req = nil
	ctx.Resp = nil
	ctx.RequestCtx = nil
	if ctx.tr != nil {
		ctx.tr.Finish(nil)
		ctx.tr = nil
	}
	ctx.stacktrace = nil
	ctx.reason = nil
	ctxPool.Put(ctx)
}

// Helpers

func (ctx *Ctx) flush() {
	if ctx.Resp != nil {
		ctx.SetContentType(ctx.GetContentType())
		ctx.Flush(ctx, true)
	}
}

func (ctx *Ctx) GetStacktrace() []byte {
	return ctx.stacktrace
}

func (ctx *Ctx) DumpResp(w io.Writer) {
	if ctx.Resp != nil {
		ctx.Flush(w, false)
	}
	if len(ctx.reason) != 0 {
		w.Write(ctx.reason)
	}
}

func (ctx *Ctx) Status(status int, reason []byte) {
	ctx.reason = reason
	ctx.RequestCtx.SetStatusCode(status)
}

func (ctx *Ctx) SetResponseHeader(key, val string) {
	ctx.RequestCtx.Response.Header.Set(key, val)
}

func (ctx *Ctx) NotFound(reason []byte) {
	ctx.reason = reason
	ctx.RequestCtx.NotFound()
}

func (ctx *Ctx) WriteStatus(status int, msg string, keyvals ...interface{}) {
	if ctx.Proto == nil {
		reason := utils.Str2Bytes(msg)
		ctx.Status(status, reason)
		ctx.SetBody(reason)
		return
	}
	if ctx.Resp == nil {
		ctx.Resp = ctx.NewResponse()
	}
	ctx.Resp.WriteStatus(status, msg, keyvals...)
}

func (ctx *Ctx) WriteKeyvals(keyvals ...interface{}) {
	if ctx.Resp == nil {
		ctx.Resp = ctx.NewResponse()
	}
	ctx.Resp.WriteKeyvals(keyvals...)
}

var jsonContentType = "application/json; charset=utf-8"

func (ctx *Ctx) WriteJson(keyvals interface{}) {
	j, err := json.MarshalIndent(keyvals, "", "  ")
	if err != nil {
		olog.Printf("error:", err)
	}
	ctx.SetContentType(jsonContentType)
	ctx.Write(j)
	//return ctx.GetContentType()
}

func (ctx *Ctx) WriteResult(result interface{}) {
	if ctx.Resp == nil {
		ctx.Resp = ctx.NewResponse()
	}
	ctx.Resp.WriteResult(result)
}

func (ctx *Ctx) Ok(msg ...string) {
	if len(msg) != 0 {
		ctx.WriteStatus(http.StatusOK, msg[0])
	} else {
		ctx.WriteStatus(http.StatusOK, "ok")
	}
}

func (ctx *Ctx) code(code errors.Code, trace bool) *Ctx {
	ctx.WriteStatus(int(code), code.Error())
	return ctx
}

func (ctx *Ctx) Code(code errors.Code) *Ctx {
	return ctx.code(code, true)
}

func (ctx *Ctx) Error(err error) {
	if err == nil {
		return
	}
	if ctx.tr != nil {
		ctx.tr.Finish(err)
		ctx.tr = nil
	}
	if e, ok := err.(*errors.Stack); ok {
		log.Warn("req_id", ctx.reqID, "err", e.Error(), "name", ctx.name, "trace", e.Trace())
	} else {
		log.Warn("req_id", ctx.reqID, "err", err.Error(), "name", ctx.name)
	}
	err = errors.Cause(err)
	if code, ok := err.(errors.Code); ok {
		ctx.code(code, true)
		return
	}
	if v, ok := err.(*errors.Verbose); ok {
		ctx.WriteStatus(int(v.Code), v.Desc)
		return
	}
	ctx.code(errors.CodeInternal, false)
}

func (ctx *Ctx) GetHeader(key string) []byte {
	return ctx.Request.Header.Peek(key)
}

func (ctx *Ctx) peek(key string) []byte {
	return bytes.TrimSpace(ctx.FormValue(key))
}

func (ctx *Ctx) FormString(key string) (string, error) {
	val := ctx.peek(key)
	if len(val) == 0 {
		return "", errors.BadRequest("value is missing: " + key)
	}
	return *(*string)(unsafe.Pointer(&val)), nil
}

func (ctx *Ctx) FormBytes(key string) ([]byte, error) {
	val := ctx.peek(key)
	if len(val) == 0 {
		return nil, errors.BadRequest("value is missing: " + key)
	}
	return val, nil
}

func (ctx *Ctx) FormInt64(key string) (int64, error) {
	val, err := ctx.FormString(key)
	if err != nil {
		return 0, err
	}
	return strconv.ParseInt(val, 10, 64)
}

func (ctx *Ctx) FormUint64(key string) (uint64, error) {
	val, err := ctx.FormString(key)
	if err != nil {
		return 0, err
	}
	return strconv.ParseUint(val, 10, 64)
}

func (ctx *Ctx) FormInt(key string) (int, error) {
	val, err := ctx.FormString(key)
	if err != nil {
		return 0, err
	}
	return strconv.Atoi(val)
}

func (ctx *Ctx) FormUint(key string) (uint, error) {
	val, err := ctx.FormString(key)
	if err != nil {
		return 0, err
	}
	ui, err := strconv.ParseUint(val, 10, 0)
	return uint(ui), err
}

func (ctx *Ctx) FormFloat64(key string) (float64, error) {
	val, err := ctx.FormString(key)
	if err != nil {
		return 0, err
	}
	return strconv.ParseFloat(val, 64)
}

func (ctx *Ctx) VarsString(key string) (string, error) {
	val := ctx.params.ByName(key)
	if val == "" {
		return "", errors.BadRequest("value is missing: " + key)
	}
	return val, nil
}

func (ctx *Ctx) VarsInt64(key string) (int64, error) {
	val := ctx.params.ByName(key)
	if val == "" {
		return 0, errors.BadRequest("value is missing: " + key)
	}
	return strconv.ParseInt(val, 10, 64)
}

func (ctx *Ctx) FromJson(dst interface{}) error {
	if err := json.NewDecoder(bytes.NewReader(ctx.PostBody())).Decode(dst); err != nil {
		if err == io.EOF {
			return errors.BadRequest("body is missing")
		}
		return errors.BadRequest("invalid json data")
	}
	return nil
}

func (ctx *Ctx) FromXml(dst interface{}) error {
	return xml.NewDecoder(bytes.NewReader(ctx.PostBody())).Decode(dst)
}
